(module bag mzscheme
  
  (require "private/require.ss")
  (require-contracts)
  (require-lists)
  (require-etc)
  (require-class)
  
  (require ;"private/contracts.ss"
           "bag/bag-interface.ss"
           "bag/hashed-bag.ss"
           "bag/ordered-bag.ss"
           "bag/simple-bag.ss"
           "bag/unordered-bag.ss")
  
  ;; For predicates that also take a count of elements
  (define predicate/count/c
    (any/c positive? . -> . any/c))
  
  (provide/contract
   ;;; Type predicate and contract
   [bag? predicate/c]
   [bag/c flat-contract?]
   [non-empty-bag/c flat-contract?]
   [bag-of/c (flat-contract/c . -> . flat-contract?)]
   [non-empty-bag-of/c (flat-contract/c . -> . flat-contract?)]
   ;;; Constructors
   ;; Single argument, list of elements
   [list->hashed (hash-fn/c equality/c (listof any/c) . -> . bag/c)]
   [list->ordered (comparison/c (listof any/c) . -> . bag/c)]
   [list->unordered (equality/c (listof any/c) . -> . bag/c)]
   [list->eq ((listof any/c) . -> . bag/c)]
   [list->eqv ((listof any/c) . -> . bag/c)]
   [list->equal ((listof any/c) . -> . bag/c)]
   ;; Variable-arity, elements
   [make-hashed ([hash-fn/c equality/c] (listof any/c) . ->* . [bag/c])]
   [make-ordered ([comparison/c] (listof any/c) . ->* . [bag/c])]
   [make-unordered ([equality/c] (listof any/c) . ->* . [bag/c])]
   [make-eq ([] (listof any/c) . ->* . [bag/c])]
   [make-eqv ([] (listof any/c) . ->* . [bag/c])]
   [make-equal ([] (listof any/c) . ->* . [bag/c])]
   ;;; Operations
   [elements (bag/c . -> . (listof any/c))]
   [to-sexp   (bag/c . -> . (listof (list/c any/c positive?)))]
   [to-alist  (bag/c . -> . (listof (cons/c any/c positive?)))]
   [rename bag-empty? empty? (bag/c . -> . boolean?)]
   [count  (any/c bag/c . -> . natural-number/c)]
   [member? (any/c bag/c . -> . boolean?)]
   [select (non-empty-bag/c . -> . any/c)]
   [select/count (non-empty-bag/c . -> . (values any/c positive?))]
   [clear  (bag/c . -> . bag/c)]
   [size   (bag/c . -> . natural-number/c)]
   [size/unique (bag/c . -> . natural-number/c)]
   [subbag? (bag/c bag/c . -> . boolean?)]
   [rename bag-equal? equal? (bag/c bag/c . -> . boolean?)]
   ;; Operates on a single element, doesn't use count
   [insert (any/c bag/c . -> . non-empty-bag/c)]
   [lookup ([any/c bag/c] [(-> any) (any/c . -> . any)] . opt-> . any)]
   [rename bag-remove remove (any/c bag/c . -> . bag/c)]
   [remove/all (any/c bag/c . -> . bag/c)]
   [rename bag-fold fold ((any/c any/c . -> . any/c) any/c bag/c . -> . any/c)]
   [rename bag-map map ((any/c . -> . any/c) bag/c . -> . bag/c)]
   [rename bag-for-each for-each ((any/c . -> . any) bag/c . -> . void?)]
   [rename bag-filter filter ((any/c . -> . any/c) bag/c . -> . bag/c)]
   [any? ((any/c . -> . any/c) bag/c . -> . boolean?)]
   [rename bag-ormap ormap ((any/c . -> . any/c) bag/c . -> . boolean?)]
   [all? ((any/c . -> . any/c) bag/c . -> . boolean?)]
   [rename bag-andmap andmap ((any/c . -> . any/c) bag/c . -> . boolean?)]
   ;; Operates on multiple elements (variable-arity), doesn't use count
   [insert* ([bag/c] (listof any/c) . ->* . [bag/c])]
   [rename bag-remove* remove* ([bag/c] (listof any/c) . ->* . [bag/c])]
   ;; Operates on a single element, uses count
   [insert/count (any/c natural-number/c bag/c . -> . bag/c)]
   [lookup/count ([any/c bag/c] [(-> any) (any/c positive? . -> . any)] . opt-> . any)]
   [remove/count (any/c natural-number/c bag/c . -> . bag/c)]
   [fold/count ((any/c positive? any/c . -> . any/c) any/c bag/c . -> . any/c)]
   [map/count ((any/c positive? . -> . any/c) bag/c . -> . bag/c)]
   [for-each/count ((any/c positive? . -> . any) bag/c . -> . void?)]
   [filter/count (predicate/count/c bag/c . -> . bag/c)]
   [any?/count (predicate/count/c bag/c . -> . boolean?)]
   [ormap/count (predicate/count/c bag/c . -> . boolean?)]
   [all?/count (predicate/count/c bag/c . -> . boolean?)]
   [andmap/count (predicate/count/c bag/c . -> . boolean?)]
   ;; Operates on more than one bag.  Result assumes the properties of the first bag.
   [union (bag/c bag/c . -> . bag/c)]
   [intersection (bag/c bag/c . -> . bag/c)]
   [difference (bag/c bag/c . -> . bag/c)]
   )
  
  ;; make-hashed : (Elem -> Integer) (Elem Elem -> Boolean) Elem ... -> (Bag Elem)
  ;; Constructs a bag with hashed elements.
  (define (make-hashed hash equ? . elems)
    (make-hashed-bag hash equ? elems))
  (define list->hashed make-hashed-bag)
  
  ;; make-ordered : (Elem Elem -> (Union -1 0 1)) Elem ... -> (Bag Elem)
  ;; Constructs a bag with ordered elements.
  (define (make-ordered compare . elems)
    (make-ordered-bag compare elems))
  (define list->ordered make-ordered-bag)
  
  ;; make-unordered : (Elem Elem -> Boolean) Elem ... -> (Bag Elem)
  ;; Constructs a bag with unordered elements.
  (define (make-unordered equ? . elems)
    (make-unordered-bag equ? elems))
  (define list->unordered make-unordered-bag)
  
  ;; make-eq : Elem ... -> (Bag Elem)
  ;; Constructs a hashed bag with eq? as the equality operator
  (define (list->eq elems)
    (make-hashed-bag eq-hash-code eq? elems))
  (define (make-eq . elems)
    (list->eq elems))
  
  ;; make-eqv : Elem ... -> (Bag Elem)
  ;; Constructs an unordered bag with eqv? as the equality operator
  (define (list->eqv elems)
    (make-unordered-bag eqv? elems))
  (define (make-eqv . elems)
    (list->eqv elems))
  
  ;; make-equal : Elem ... -> (Bag Elem)
  ;; Constructs a hashed bag with equal? as the equality operator
  (define (list->equal elems)
    (make-hashed-bag equal-hash-code equal? elems))
  (define (make-equal . elems)
    (list->equal elems))
  
  ;; elements : (Bag Elem) -> (List Elem)
  ;; Produces the elements of a bag.
  (define (elements bag)
    (send bag elements))

  ;; sexp : (Bag Elem) -> (List (List Elem Integer))
  ;; Produces an s-expression where each sublist is an element
  ;; of the bag and the number of times it appears.
  (define (to-sexp bag)
    (send bag sexp))

  ;; alist : (Bag Elem) -> (List (cons Elem Integer))
  ;; Produces an association list where each element of the bag is
  ;; associated with its count.
  (define (to-alist bag)
    (send bag alist))

  ;; insert : Elem (Bag Elem) -> (Bag Elem)
  ;; Produces an updated bag containing the new element.  If the
  ;; element was already contained by the bag, the new bag has
  ;; the element's count increased by one.
  (define (insert elem bag)
    (send bag insert elem))

  ;; insert* : (Bag Elem) Elem ... -> (Bag Elem)
  ;; Produces an updated bag containing the new elements.  If any
  ;; of the elements was already contained by the bag, the new bag
  ;; has that element's count increased by one.
  (define (insert* bag . elems)
    (send bag insert* . elems))

  ;; insert/count : Elem Integer (Bag Elem) -> (Bag Elem)
  ;; Produces an updated bag containing the new element.  If the
  ;; element was already contained by the bag, the new bag has
  ;; the element's count increased by the given count.
  (define (insert/count elem count bag)
    (send bag insert/count elem count))

  ;; lookup : Elem (Bag Elem) [(-> A) (Elem -> B)] -> (Union A B)
  ;; Looks up an element of a bag.
  ;; If found, calls the success function (default: return the element).
  ;; If not found, calls the failure function (default: return #f).
  (define (lookup elem bag . rest)
    (send bag lookup elem . rest))

  ;; lookup : Elem (Bag Elem) [(-> A) (Elem Integer -> B)] -> (Union A B)
  ;; Looks up an element of a bag.
  ;; If found, calls the success function with the element and the
  ;; element count (default: return the element).
  ;; If not found, calls the failure function (default: return #f).
  (define (lookup/count elem bag . rest)
    (send bag lookup/count elem . rest))
  
  ;; count : Elem (Bag Elem) -> Integer
  ;; Returns the amount of times the element appears in the bag.
  (define (count elem bag)
    (send bag count elem))
  
  ;; select : (Bag Elem) -> Elem
  ;; Returns an element from the bag.
  (define (select bag)
    (send bag select))
  
  ;; select/count : (Bag Elem) -> (values Elem PositiveNumber)
  ;; Returns an element and its count from the bag.
  (define (select/count bag)
    (send bag select/count))
  
  ;; bag-empty? : (Bag Elem) -> Boolean
  ;; Returns whether the given bag is empty.
  (define (bag-empty? bag)
    (send bag empty?))

  ;; size : (Bag Elem) -> Integer
  ;; Returns the size of the bag in total number of elements.
  (define (size bag)
    (send bag size))
  
  ;; size/unique : (Bag Elem) -> Integer
  ;; Returns the size of the bag in total number of unique
  ;; elements.
  (define (size/unique bag)
    (send bag size/unique))
  
  ;; member? : Elem (Bag Elem) -> Boolean
  ;; Returns whether a given element is in the bag or not.
  (define (member? elem bag)
    (send bag member? elem))
  
  ;; clear : (Bag Elem) -> (Bag Elem)
  ;; Returns a new bag having the same properties as the given bag.
  (define (clear bag)
    (send bag clear))
  
  ;; remove : Elem (Bag Elem) -> (Bag Elem)
  ;; Returns a new bag with all the same elements as the given bag
  ;; except for if the given element is a member.  If there is
  ;; only one of the given element, the new bag will not contain
  ;; it, else the new bag will contain one less of that element.
  (define (bag-remove elem bag)
    (send bag remove elem))

  ;; remove* : (Bag Elem) Elem ... -> (Bag Elem)
  ;; Returns a new bag with all the same elements as the given bag
  ;; except for if any of the given elements is a member.  If there
  ;; is only one of a given element, the new bag will not contain
  ;; it, else the new bag will contain one less of that element.
  (define (bag-remove* bag . elems)
    (send bag remove* .  elems))

  ;; remove/all : Elem (Bag Elem) -> (Bag Elem)
  ;; Returns a new bag with all the same elements as the given bag
  ;; except for if the given element is a member.  There will be
  ;; no occurrences of the given element in the new bag.
  (define (remove/all elem bag)
    (send bag remove/all elem))
  
  ;; remove/count : Elem Integer (Bag Elem) -> (Bag Elem)
  ;; Returns a new bag with all the same elements as the given bag
  ;; except for if the given element is a member.  If there is
  ;; a number of the given element less than or equal to the given
  ;; amount, the new bag will not contain it, else the new bag will
  ;; contain that many less of the given element.
  (define (remove/count elem count bag)
    (send bag remove/count elem count))

  ;; fold : (Elem T -> T) T (Bag Elem) -> T
  ;; Builds up a result from each element and element count in
  ;; the bag.
  (define (bag-fold f i bag)
    (send bag fold f i))
  
  ;; fold/count : (Elem Integer T -> T) T (Bag Elem) -> T
  ;; Builds up a result from each element and element count in
  ;; the bag.
  (define (fold/count f i bag)
    (send bag fold/count f i))
  
  ;; map : (Elem -> Elem) (Bag Elem) -> (Bag Elem)
  ;; Transforms each element in the bag using the given function.
  (define (bag-map f bag)
    (send bag map f))

  ;; map/count : (Elem PositiveNumber -> Elem) (Bag Elem) -> (Bag Elem)
  ;; Like map, but the transformer also takes an element count.
  (define (map/count f bag)
    (send bag map/count f))

  ;; for-each : (Elem -> Void) (Bag Elem) -> Void
  ;; Performs an action for each element in the bag.
  (define (bag-for-each f bag)
    (send bag for-each f))
  
  ;; for-each/count : (Elem Integer -> Void) (Bag Elem) -> Void
  ;; Performs an action for each element and element count in
  ;; the bag.
  (define (for-each/count f bag)
    (send bag for-each/count f))
  
  ;; filter : (Elem -> Boolean) (Bag Elem) -> (Bag Elem)
  ;; Filters out any elements in the bag that do not conform
  ;; to the predicate.
  (define (bag-filter f bag)
    (send bag filter f))
  
  ;; all? : (Elem -> Boolean) (Bag Elem) -> Boolean
  ;; Returns whether every element in the bag matches the predicate.
  (define (all? f bag)
    (send bag all? f))
  (define bag-andmap all?)
  
  ;; any? : (Elem -> Boolean) (Bag Elem) -> Boolean
  ;; Returns whether every element in the bag matches the predicate.
  (define (any? f bag)
    (send bag any? f))
  (define bag-ormap any?)

  ;; filter/count : (Elem Integer -> Boolean) (Bag Elem) -> (Bag Elem)
  ;; Like filter, but the predicate also takes an element count.
  (define (filter/count f bag)
    (send bag filter/count f))
  
  ;; all?/count : (Elem Integer -> Boolean) (Bag Elem) -> Boolean
  ;; Like all?, but the predicate also takes an element count.
  (define (all?/count f bag)
    (send bag all?/count f))
  (define andmap/count all?/count)
  
  ;; any?/count : (Elem Integer -> Boolean) (Bag Elem) -> Boolean
  ;; Like any?, but the predicate also takes an element count.
  (define (any?/count f bag)
    (send bag any?/count f))
  (define ormap/count any?/count)
  
  ;; union : (Bag Elem) (Bag Elem) : (Bag Elem)
  ;; Takes the union of the two bags given as an argument.
  ;; The element count for a given element will be the maximum of
  ;; the element count for each bag.
  (define (union b1 b2)
    (send b1 union b2))

  ;; intersection : (Bag Elem) (Bag Elem) : (Bag Elem)
  ;; Takes the intersection of the two bags given as an argument.
  ;; The element count for a given element will be the minimum of
  ;; the element count for each bag.
  (define (intersection b1 b2)
    (send b1 intersection b2))
  
  ;; difference : (Bag Elem) (Bag Elem) : (Bag Elem)
  ;; Returns the first bag with all elements from the second
  ;; bag removed.  That means the element count for a given
  ;; element will be the element count for the first bag minus
  ;; the element count for the second bag.
  (define (difference b1 b2)
    (send b1 difference b2))
  
  ;; subbag? : (Bag Elem) (Bag Elem) : Boolean
  ;; Returns whether the second bag contains all the elements
  ;; that the first contains.  This also means that the second
  ;; must contain at least as many occurrences of each element
  ;; as the first does.
  (define (subbag? b1 b2)
    (send b1 subbag? b2))
  
  ;; equal? : (Bag Elem) (Bag Elem) : Boolean
  ;; Returns whether the two bags are equal (i.e. have the same
  ;; elements and the same number of occurrences of each element).
  (define (bag-equal? b1 b2)
    (send b1 equal? b2))
  )
